Skip to main content

Exploration Tools

Tutorials

When faced with a binary with no source or parts of the source missing you can infer some of its functionalities based upon some basic reconnaissance techniques using various tools.

01. Tutorial - Poor Man's Technique: strings

The simplest recon technique is to dump the ASCII (or Unicode) text from a binary. It doesn't offer any guarantees but sometimes you can get a lot of useful information out of it.

By default, when applied to a binary it only scans the data section. To obtain information such as the compiler version used in producing the binary use:

strings -a crackme1

Let's illustrate how strings can be useful in a simple context. Try out the crackme1 binary:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int my_strcmp(char *s1, char *s2)
{
size_t i, len = strlen(s1);
if (len == 0)
return -1;
for (i = 0; i < len; i++)
if (s1[i] != s2[i])
return -1;

return 0;
}

int main()
{
char buf[1000];

printf("Password:\n");
if (fgets(buf, 1000, stdin) == NULL)
exit(-1);

buf[strlen(buf) - 1] = '\0';

if (!my_strcmp(buf, ???????????????????????)) {
printf("Correct!\n");
} else
printf("Nope!\n");

return 0;
}

The password has been redacted from the listing but you can retrieve it with strings. Try it out!

02. Tutorial - Execution Tracing (ltrace and strace)

ltrace is an utility that can list library function calls or syscalls made by a program. strace is similar, but only lists syscalls. A syscall is a service exposed by the kernel itself.

The way they work is with the aid of a special syscall, called ptrace. This single syscall forms the basis for most of the functionality provided by ltrace, strace, gdb and similar tools that debug programs. It can receive up to 4 arguments: the operation, the PID to act on, the address to read/write and the data to write. The functionality exposed by ptrace() is massive, but think of any functionality you've seen in a debugger:

  • attach/detach to/from a process
  • set breakpoints
  • continue a stopped program
  • read/write registers
  • act on signals
  • register syscalls

strace provides some pretty printing strictly concerning the syscalls of the traced process. However, ltrace provides further functionality and gathers information about all library calls. Here's how ltrace does its magic:

  • it reads the tracee memory and parses it in order to find out about loaded symbols
  • it makes a copy of the binary code pertaining to a symbol using a PTRACE_PEEKTEXT directive of ptrace()
  • it injects a breakpoint using a PTRACE_POKETEXT directive of ptrace()
  • it listens for a SIGTRAP which will be generated when the breakpoint is hit
  • when the breakpoint is hit, ltrace can examine the stack of the tracee and print information such as function name, parameters, return codes, etc.

Let's try the next crackme. If we remove my_strcmp from the previous crackme you can solve it even without strings because strcmp is called from libc.so. You can use ltrace and see what functions are used and check for their given parameters. Try it out on the second crackme where strings does not help (crackme2):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char correct_pass[] = ????????????????? ;
char *deobf(char *s)
{
???????????????
}

int main()
{
char buf[1000];

printf("Password:\n");
if (fgets(buf, 1000, stdin) == NULL)
exit(-1);

buf[strlen(buf) - 1] = '\0';

if (!strcmp(buf, deobf(correct_pass)))
printf("Correct!\n");
else
printf("Nope!\n");

return 0;
}

03. Tutorial - Symbols: nm

Symbols are basically tags/labels, either for functions or for variables. If you enable debugging symbols you will get information on all the variables defined but normally symbols are only defined for functions and global variables. When stripping binaries even these can be deleted without any effect on the binary behavior. Dynamic symbols, however, have to remain so that the linker knows what functions to import:

$ file xy
xy: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.16, not stripped

$ nm xy
0804a020 B __bss_start
0804a018 D __data_start
0804a018 W data_start
0804a01c D __dso_handle
08049f0c d _DYNAMIC
0804a020 D _edata
0804a024 B _end
080484e4 T _fini
080484f8 R _fp_hw
0804a000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
080482d4 T _init
08049f04 t __init_array_end
08049f00 t __init_array_start
080484fc R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
080484e0 T __libc_csu_fini
08048470 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804843c T main
U puts@@GLIBC_2.0
08048340 T _start
0804a020 D __TMC_END__
08048370 T __x86.get_pc_thunk.bx
$ strip xy
$ nm xy
nm: xy: no symbols
$ nm -D xy
w __gmon_start__
080484fc R _IO_stdin_used
U __libc_start_main
U puts

Let's take a look at another crackme that combines crackme1 and crackme2. What would you do if you couldn't use neither strings nor ltrace to get anything useful?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


char correct_pass[] = ????????????????????????

int my_strcmp(char *s1, char *s2)
{
size_t i, len = strlen(s1);
if (len == 0)
return -1;
for (i = 0; i < len; i++)
if (s1[i] != s2[i])
return -1;

return 0;
}

char *deobf(char *s)
{
???????????????????????????
}

int main()
{
char buf[1000];
deobf(correct_pass);
printf("Password:\n");
if (fgets(buf, 1000, stdin) == NULL)
exit(-1);

buf[strlen(buf) - 1] = '\0';

if (!my_strcmp(buf, correct_pass)) {
printf("Correct!\n");
} else
printf("Nope!\n");

return 0;
}

In crackme3, deobfuscation is done before the password is read. Since the correct_pass has an associated symbol that is stored at a known location you can obtain the address and peer into it at runtime:

$ nm crackme3 | grep pass
0804a02c D correct_pass
$ gdb -n ./crackme3
Reading symbols from ./crackme3...(no debugging symbols found)...done.
(gdb) run
Password:
^C
Program received signal SIGINT, Interrupt.
0xf7fdb430 in __kernel_vsyscall ()
(gdb) x/s 0x0804a02c
0x804a02c <correct_pass>: "JWxb7gE2pjiY3gRG8U"

The above x/s 0x0804a02c command in GDB is used for printing the string starting from address 0x0804a02c. x stands for examine memory and s stands for string format. In short it dumps memory in string format starting from the address passed as argument. You may print multiple strings by prefixing s with a number, for example x/20s 0x0804a02c.

For other programs (that are not stripped) you can even get a hint as to what they do using solely nm:

$ nm mystery_binary
.....
0000000000402bef T drop_privs(char const*)
00000000004027db T IndexHandler(std::string const&, HttpRequest const&, HttpResponse*)
0000000000402ad8 T StatusHandler(std::string const&, HttpRequest const&, HttpResponse*)
000000000040237f T NotFoundHandler(std::string const&, HttpRequest const&, HttpResponse*)
00000000004024a1 T BadRequestHandler(std::string const&, HttpRequest const&, HttpResponse*)
00000000004025c3 T MaybeAddCORSHeader(std::string const&, HttpRequest const&, HttpResponse*)
0000000000402f52 t __static_initialization_and_destruction_0(int, int)
0000000000402cf8 T handle(int)
00000000004020fc T recvlen(int, char*, unsigned long)
0000000000402195 T sendlen(int, char const*, unsigned long)
0000000000402224 T sendstr(int, char const*)
0000000000402255 T urldecode(std::string const&)
.....

Note: In this case the signatures are also decoded because the binary was compiled from C++ source code.

Dealing with stripped binaries (or worse, statically linked binaries that have been stripped) is harder but can still be done. We'll see how in a future lab.

04. Tutorial - Library Dependencies

Most programs you will see make use of existing functionality. You don't want to always reimplement string functions or file functions. Therefore, most programs use dynamic libraries. These shared objects, as they are called alternatively, allow you to have a smaller program and also allow multiple programs to use a single copy of the code within the library. But how does that actually work?

What makes all of these programs work is the Linux dynamic linker/loader. This is a statically linked helper program that resolves symbol names from shared objects at runtime. We can use the dynamic linker to gather information about an executable.

The first and most common thing to do is see what libraries the executable loads, with the ldd utility:

$ ldd /bin/ls
linux-vdso.so.1 (0x00007ffff13fe000)
librt.so.1 => /lib64/librt.so.1 (0x00007fc9b4893000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007fc9b468a000)
libc.so.6 => /lib64/libc.so.6 (0x00007fc9b42da000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc9b40bd000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007fc9b3eb8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc9b4a9b000)

We see that for each dependency in the executable, ldd lists where it is found on the filesystem and where it is loaded in the process memory space. Alternatively, you can achieve the same result with the LD_TRACE_LOADED_OBJECTS environment variable, or with the dynamic loader itself:

$ LD_TRACE_LOADED_OBJECTS=whatever /bin/ls
linux-vdso.so.1 (0x00007fff325fe000)
librt.so.1 => /lib64/librt.so.1 (0x00007f1845386000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007f184517d000)
libc.so.6 => /lib64/libc.so.6 (0x00007f1844dcd000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1844bb0000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f18449ab000)
/lib64/ld-linux-x86-64.so.2 (0x00007f184558e000)
$ /lib/ld-linux-x86-64.so.2 --list /bin/ls
linux-vdso.so.1 (0x00007fff1e712000)
librt.so.1 => /lib64/librt.so.1 (0x00007f18a07d8000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007f18a05cf000)
libc.so.6 => /lib64/libc.so.6 (0x00007f18a021e000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f189fdfc000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f18a0001000)
/lib64/ld-linux-x86-64.so.2 => /lib/ld-linux-x86-64.so.2 (0x00007f18a0c44000)

When using the loader directly, make sure the loader and the executable are compiled for the same platform (e.g. they are both 64-bit or 32-bit). You may find out more information about dynamic linker/loader variables in its man page. Issue the command:

man ld-linux.so

and search for the LD_ string to find variables information.

ldd shows us which libraries are loaded, but it's not any clearer how the loader knows where to load them from. First of all, the loader checks every dependency for a slash character. If it finds such a dependency it loads the library from that path, whether it is a relative of absolute path. But it is not the case in our example. For dependencies without slashes, the search order is as follows:

  • DT_RPATH attribute in the .dynamic section of the executable, provided there is no DT_RUNPATH; this is deprecated
  • LD_LIBRARY_PATH environment variable, which is similar to PATH; does not work with setuid / setgid programs
  • DT_RUNPATH attribute in the .dynamic section of the executable
  • /etc/ld.so.cache, generated by ldconfig
  • /lib and then /usr/lib

The last two options are skipped if the program was linked with the -z nodeflib option.

Now let's see exactly where the loader finds the libraries:

$ LD_DEBUG=libs /bin/ls
11451: find library=librt.so.1 [0]; searching
11451: search cache=/etc/ld.so.cache
11451: trying file=/lib64/librt.so.1
11451:
11451: find library=libacl.so.1 [0]; searching
11451: search cache=/etc/ld.so.cache
11451: trying file=/lib64/libacl.so.1
11451:
11451: find library=libc.so.6 [0]; searching
11451: search cache=/etc/ld.so.cache
11451: trying file=/lib64/libc.so.6
11451:
11451: find library=libpthread.so.0 [0]; searching
11451: search cache=/etc/ld.so.cache
11451: trying file=/lib64/libpthread.so.0
11451:
11451: find library=libattr.so.1 [0]; searching
11451: search cache=/etc/ld.so.cache
11451: trying file=/lib64/libattr.so.1

The LD_DEBUG environment variable makes the dynamic loader be verbose about what it's doing. Try LD_DEBUG=help if you're curious about what else you can find out. We can see in the output listed above that all the libraries are found via the loader cache. The number at the beginning of each line is the PID of the ls process.

And now we can discuss how the loader resolves symbols after it has found the libraries containing them. While variables are resolved when the library is opened, that is not the case for function references. When dealing with functions, the Linux dynamic loader uses something called lazy binding, which means that a function symbol in the library is not resolved until the very first call to it. Think about why this difference exists.

You can see the way lazy binding behaves:

$ LD_DEBUG=symbols,bindings ./crackme2
...
11480: initialize program: ./crackme2
11480:
11480:
11480: transferring control: ./crackme2
11480:
11480: symbol=puts; lookup in file=./crackme2 [0]
11480: symbol=puts; lookup in file=/lib32/libc.so.6 [0]
11480: binding file ./crackme2 [0] to /lib32/libc.so.6 [0]: normal symbol 'puts' [GLIBC_2.0]
Password:
11480: symbol=fgets; lookup in file=./crackme2 [0]
11480: symbol=fgets; lookup in file=/lib32/libc.so.6 [0]
11480: binding file ./crackme2 [0] to /lib32/libc.so.6 [0]: normal symbol 'fgets' [GLIBC_2.0]
I_pity_da_fool_who_gets_here_without_solving_crackme2
11480: symbol=strlen; lookup in file=./crackme2 [0]
11480: symbol=strlen; lookup in file=/lib32/libc.so.6 [0]
11480: binding file ./crackme2 [0] to /lib32/libc.so.6 [0]: normal symbol 'strlen' [GLIBC_2.0]
11480: symbol=strcmp; lookup in file=./crackme2 [0]
11480: symbol=strcmp; lookup in file=/lib32/libc.so.6 [0]
11480: binding file ./crackme2 [0] to /lib32/libc.so.6 [0]: normal symbol 'strcmp' [GLIBC_2.0]
Nope!
11480:
11480: calling fini: ./crackme2 [0]
11480:

As you can see, functions like puts(), fgets(), strlen() and strcmp() are not actually resolved until the first call to them is made. Make the loader resolve all the symbols at startup. (Hint: ld-linux).

Library Wrapper Task

You've previously solved crackme2 with the help of the ltrace. Check out the files from 04-tutorial-library-dependencies. The folder consists of a Makefile and a C source code file reimplementing the strcmp() function (library wrapper). The strcmp.c implementation uses LD_PRELOAD to wrap the actual strcmp() call to our own.

In order to see how that works, we need to create a shared library and pass it as an argument to LD_PRELOAD. The Makefile already takes care of this. To build and run the entire thing, simply run:

make run

This will build the shared library file (strcmp.so) and run the crackme2 executable under LD_PRELOAD.

Our goal is to use the strcmp() wrapper to alter the program behavior. We have two ways to make the crackme2 program behave our way:

  1. Leak the password in the strcmp() wrapper.
  2. Pass the check regardless of what password we provide.

Modify the strcmp() function in the strcmp.c source code file to alter the crackme2 program behavior in each of the two ways shown above. To test it, use the Makefile:

make run

05. Tutorial - Network: netstat and netcat

Services running on remote machines offer a gateway to those particular machines. Whether it's improper handling of the data received from clients, or a flaw in the protocol used between server and clients, certain privileges can be obtained if care is not taken. We'll explore some tools and approaches to analyzing remote services. To follow along, use the server and client programs from 05-tutorial-network-netstat-netcat.

First of all, start the server:

$ ./server
Welcome to the awesome server.
Valid commands are:
quit
status

Running any of them at this point doesn't offer much help. We'll come back to this later.

The most straightforward way to see what a server does is the netstat utility.

$ netstat -tlpn
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:36732 0.0.0.0:* LISTEN 3062/steam
tcp 0 0 127.0.0.1:57343 0.0.0.0:* LISTEN 3062/steam
tcp 0 0 127.0.0.1:31337 0.0.0.0:* LISTEN 15022/./server
tcp 0 0 0.0.0.0:58154 0.0.0.0:* LISTEN 3062/steam
tcp 0 0 127.0.0.1:60783 0.0.0.0:* LISTEN 2644/SpiderOak
tcp 0 0 192.168.101.1:53 0.0.0.0:* LISTEN -
tcp 0 0 192.168.100.1:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:44790 0.0.0.0:* LISTEN 2644/SpiderOak
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp6 0 0 :::631 :::* LISTEN -

Here we're looking at all the programs that are listening (-l) on a TCP port (-t). We're also telling netcat not to resolve hosts (-n) and to show the process that is listening (-p). We can see that our server is listening on port 31337. Let's keep that in mind and see how the client behaves:

$ ./client
Usage: ./client <client name> <server IP> <server port>

$ ./client the_laughing_man localhost 31337
Welcome to the awesome server.
Valid commands are:
listclients
infoclient <client name> [ADMIN access required]
name, IP, port, privileged, connected time
sendmsg <client name> <message>

Enter a command (or 'quit' to exit):
listclients
Connected clients are:
the_laughing_man
Enter a command (or 'quit' to exit):
sendmsg the_laughing_man test
Enter a command (or 'quit' to exit):
Message from the_laughing_man
test

Enter a command (or 'quit' to exit):
infoclient the_laughing_man
Not enough minerals!

Enter a command (or 'quit' to exit):

So we can do anything except the privileged command infoclient. Running status on the server yields no information. What can we do now?

We can see what the server and client are exchanging at an application level by capturing the traffic with the tcpdump utility. Start tcpdump, the server and then the client, and run the commands again. When you're done, stop tcpdump with Ctrl+C.

# tcpdump -i any -w crackme5.pcap 'port 31337'
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
^C21 packets captured
42 packets received by filter
0 packets dropped by kernel

Here we're telling tcpdump to listen on all available interfaces, write the capture to the crackme5.pcap file and only log packets that have the source or destination port equal to 31337.

We can then open our capture with Wireshark in order to analyze the packets in a friendlier manner. You can look at the packets exchanged between server and client. Notice that there seems to be some sort of protocol where values are delimited by the pipe character. What is especially interesting is the first data packet sent from the client to the server, which sends the_laughing_man|false. While we've specified the client name, there was nothing we could specify via the client command-line in order to control the second value.

However, since this seems to be a plain text protocol, there is an alternative course of action available. The netcat utility allows for arbitrary clients and servers. It just needs a server address and a server port in client mode. We can use it instead of the "official" client and see what happens when we craft the first message. Go ahead! Start the server again and a normal client.

Connect to the server using netcat. Then send out the required string through the netcat connection with true as the second parameter and see if you can find out anything about the normal client.

# netcat localhost 31337
Welcome to the awesome server.
Valid commands are:
listclients
infoclient <client name> [ADMIN access required]
name, IP, port, privileged, connected time
sendmsg <client name> <message>

Doing it in Python

You can create a sever and a client in Python only. We can use the server.py and client.py scripts. Check them out first.

Then run the server by using:

python server.py

It now accepts connections on TCP port 9999 as you can see by using netstat:

$ netstat -tlpn
[...]
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:9999 0.0.0.0:* LISTEN 12541/python
[...]

Now you can test it using the Python client:

$ python client.py
sending 'anaaremere'
received 'ANAAREMERE'

We can do the same using netcat as the client:

$ nc localhost 9999
anaaremere
ANAAREMERE

Doing it Only with netcat

We can still simulate a network connection using netcat only, both for starting the server and for running the client.

Start the server with:

nc -l -p 4444

Now run the client and send messages by writing them to standard input:

$ nc localhost 4444
aaaaa
bbbbb

Messages you write to the client and up in the server.

This goes both ways: if you write messages on the server they end up in the client. Try that.

If you want to send a large chunk of data you can redirect a file. Start the server again:

nc -l -p 4444

and now send the file to it:

cat /etc/services | nc localhost 4444

It's now on the server-side.

You can also do it with UDP, instead of TCP by using the -u flag both for the server and the client. Start the server using:

nc -u -l -p 4444

And run the client using:

cat /etc/services | nc -u localhost 4444

That's how we use netcat (the network swiss army knife).

You can also look into socat for a complex tool on dealing with sockets.

06. Tutorial - Open files

Let's remember how files and programs relate in Linux.

Files

Let's also remember that, in Linux, file can mean one of many things:

  • regular file
  • directory
  • block device
  • character device
  • named pipe
  • symbolic or hard link
  • socket

Let's look at the previous server from crackme5. Start it up once again.

While previously we've used netstat to gather information about it, that was by no means the only solution. lsof is a tool that can show us what files a process has opened:

$ lsof -c server
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
server 9678 amadan cwd DIR 8,6 4096 1482770 /home/amadan/projects/sss/session01/crackmes/crackme5
server 9678 amadan rtd DIR 8,6 4096 2 /
server 9678 amadan txt REG 8,6 17524 1442625 /home/amadan/projects/sss/session01/crackmes/crackme5/server
server 9678 amadan mem REG 8,6 1753240 3039007 /lib64/libc-2.17.so
server 9678 amadan mem REG 8,6 88088 3039019 /lib64/libnsl-2.17.so
server 9678 amadan mem REG 8,6 144920 3038998 /lib64/ld-2.17.so
server 9678 amadan 0u CHR 136,2 0t0 5 /dev/pts/2
server 9678 amadan 1u CHR 136,2 0t0 5 /dev/pts/2
server 9678 amadan 2u CHR 136,2 0t0 5 /dev/pts/2
server 9678 amadan 3u IPv4 821076 0t0 TCP *:31337 (LISTEN)

We can see the standard file descriptors found in any process, as well as our socket.

  • The FD column shows the file descriptor entry for a file, or a role in case of special files. We notice the current working directory (cwd), the root directory (rtd), the current executable (txt), some memory mapped files (mem) and the file descriptors (0-3). For normal file descriptors, r means read access, w means write access and u means both.
  • The TYPE column shows whether we're dealing with a directory (DIR), a regular file (REG), a character device (CHR), a socket (IPv4) or other type of file.
  • The NODE column shows the inode of the file, or a class marker as is the case for the socket.
  • The NAME column shows the path to the file, or the bound address and port for a socket.

We've left out some details since they are not relevant for our purposes. Feel free to read the manual page.

You could also get some hint that there is an open socket by looking into the /proc virtual filesystem:

$ ls -l /proc/`pidof server`/fd
total 0
lrwx------ 1 amadan amadan 64 Jun 15 22:04 0 -> /dev/pts/2
lrwx------ 1 amadan amadan 64 Jun 15 22:04 1 -> /dev/pts/2
lrwx------ 1 amadan amadan 64 Jun 15 22:03 2 -> /dev/pts/2
lrwx------ 1 amadan amadan 64 Jun 15 22:04 3 -> socket:[883625]

We'll be using crackme6 for the next part of this section. Try the conventional means of strings and ltrace on it. Then run it normally:

$ ./crackme6
Type 'start' to begin authentication test

Before complying to what the program tells us, let's use lsof to see what we can find out:

$ lsof -c crackme6
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
crackme6 10466 amadan cwd DIR 8,6 4096 1482769 /home/amadan/projects/sss/session01/06-tutorial-open-files
crackme6 10466 amadan rtd DIR 8,6 4096 2 /
crackme6 10466 amadan txt REG 8,6 12922 5377126 /home/amadan/projects/sss/session01/06-tutorial-open-files/crackme6
crackme6 10466 amadan mem REG 8,6 1753240 3039007 /lib64/libc-2.17.so
crackme6 10466 amadan mem REG 8,6 100680 3039039 /lib64/libpthread-2.17.so
crackme6 10466 amadan mem REG 8,6 144920 3038998 /lib64/ld-2.17.so
crackme6 10466 amadan 0u CHR 136,2 0t0 5 /dev/pts/2
crackme6 10466 amadan 1u CHR 136,2 0t0 5 /dev/pts/2
crackme6 10466 amadan 2u CHR 136,2 0t0 5 /dev/pts/2
crackme6 10466 amadan 3w FIFO 0,32 0t0 988920 /tmp/crackme6.fifo
crackme6 10466 amadan 4r FIFO 0,32 0t0 988920 /tmp/crackme6.fifo

There seems to be a named pipe used by the executable. Let's look at it:

more /tmp/crackme6.fifo

Now go back again at the crackme6 console and type start. If you see the message that the authentication test has succeeded, quit and try again. If you do not see the message, kill the crackme6 process, look at the more command output and then delete the pipe file. Now try the password.

Misc

There are other sources of information available about running processes if you prefer to do things by hand such as:

  • /proc/<PID>/environ: all environment variables given when the process was started
  • /proc/<PID>/fd: opened file descriptors.
  • /proc/<PID>/mem: address space layout
  • /proc/<PID>/cwd: symlink to working directory
  • /proc/<PID>/exe: symlink to binary image
  • /proc/<PID>/cmdline: complete program command-line, with arguments

Challenges

Challenges can be found in the activities/<CHALLENGE_NUMBER>-challenge-<CHALLENGE_NAME> directory.

07. Challenge - Perfect Answer

For this task use the perfect binary.

Can you find the flag?

08. Challenge - Lots of strings

Use the lots_of_strings binary.

Can you find the password?

Hint: use the tools presented in the tutorials.

09. Challenge - Sleepy cats

For this task use the sleepy binary.

The sleep() function takes too much. Ain't nobody got time for that. We want the flag NOW!

Modify the binary in order to get the flag.

To edit a binary, you can use vim + xxd or Bless. We strongly encourage you to use Bless

10. Challenge - Hidden

For this challenge use the hidden binary.

Can you find the hidden flag?

You could use ltrace and strace to find the flag. But try to make it give you the flag by simply altering the environment, do not attach to the executable.

11. Challenge - Detective

This challenge runs remotely at 141.85.224.104:31337. You can use netcat to connect to it.

Investigate the detective binary. See what it does and work to get the flag.

You can start from the sol/exploit_template.py solution template script.

There is a bonus to this challenge and you will be able to find another flag. See that below.

Bonus: Get the Second Flag

You can actually exploit the remote detective executable and get the second flag. Look thoroughly through the executable and craft your payload to exploit the remote service.

You need to keep the connection going. Use the construction: cat /path/to/file - | nc <host> <port>

Extra

If you want some more, have a go at the bonus task. It is a simplified CTF task that you should be able to solve using the information learned in this lab.

Hint: This executable needs elevated permissions (run with sudo).

Further pwning

pwnable.kr is a wargames site with fun challenges of different difficulty levels. After completing all tutorials and challenges in this session, you should be able to go there and try your hand at the following games from Toddler's bottle: fd, collision, bof, passcode, mistake, cmd1, blukat (of course, you are encouraged to try any other challenges, but they might get frustrating, as they require knowledge of notions we will explore in future sessions).

Further Reading